package com.dhz.common.id.redis;

import java.util.concurrent.TimeUnit;

/**
 * <pre>
 *     &  - 按位与
 *     |  - 按位或
 *     ^  - 按位异或
 *     ~  - 取反
 *     << - 左移
 *     >> - 右移
 * </pre>
 *
 * Id容器
 */
public class IdObject {
    /** 逻辑分片字节长度 */
    public static final int LOGICAL_SHARD_ID_BITS = 10;
    /** 增量序列号字节长度 */
    public static final int SEQUENCE_BITS = 12;
    /** 时间戳偏移量 */
    public static final int TIMESTAMP_SHIFT = SEQUENCE_BITS + LOGICAL_SHARD_ID_BITS;
    /** 逻辑分片偏移量 */
    public static final int LOGICAL_SHARD_ID_SHIFT = SEQUENCE_BITS;
    /** 序列号的最大值bit - 4095 - 1111111111111 */
    public static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);
    /** 逻辑分片的最大值bit - 1023 - 11111111111 */
    public static final long MAX_LOGICAL_SHARD_ID = ~(-1L << LOGICAL_SHARD_ID_BITS);
    /** 逻辑分片的最小值bit - 1 - 1 */
    public static final long MIN_LOGICAL_SHARD_ID = 1L;
    /** 一次性批量获取ID数量最大值：4095+1 */
    public static final long MAX_BATCH_SIZE = MAX_SEQUENCE + 1;
    /** 一毫秒等于1000微秒 */
    public static final long ONE_MILLI_IN_MICRO_SECS = TimeUnit.MICROSECONDS.convert(1, TimeUnit.MILLISECONDS);


    /** 实际ID */
    private final long id;
    /** 毫秒 */
    private final long milliSecond;
    /** 分区 */
    private final long shardId;
    /** 序列号 */
    private final long sequence;

    /**
     * 解析ID
     * @param id Id Long
     */
    private IdObject(long id) {
        this.id = id;
        this.milliSecond = id >>> 22;
        // 2 ^ 10 -1 = 0x3FF --> 0000000001111111111
        // 将shareId位段数据抽出
        this.shardId = (id & (0x3FF << 12)) >> 12;
        // 2 ^ 12 - 1 = 0xFFF --> 000000111111111111
        this.sequence = id & 0xFFF;

    }

    /**
     * 传入基本参数，生成一个Id对象
     * @param second unix时间戳
     * @param microSecond 微秒
     * @param shardId 逻辑分片ID
     * @param sequence 序列号
     */
    public IdObject(long second, long microSecond, long shardId, long sequence) {

        // 分片ID校验
        if (shardId < MIN_LOGICAL_SHARD_ID || shardId > MAX_LOGICAL_SHARD_ID) {
            throw new IdGeneratorException(String.format("设置到Redis中使用的逻辑分片ID超出限定范围 %d ~ %d", MIN_LOGICAL_SHARD_ID, MAX_LOGICAL_SHARD_ID));
        }

        // 组装时间戳(s)和秒内微秒数
        this.milliSecond = (second * ONE_MILLI_IN_MICRO_SECS + microSecond / ONE_MILLI_IN_MICRO_SECS);
        this.shardId = shardId;
        this.sequence = sequence;
        /*
         * 构建ID数据，对原始数据做移位运算，以获得64的ID
         * 格式：1222222222222222222222222222222222222222223333333333444444444444
         * 1.作为ID的保留位，统一各平台上BIT首位的定义分歧 - 1bit
         * 2.单位为毫秒的时间戳 - 41bit
         * 3.逻辑分片ID - 10bit
         * 4.自增序列号 - 12bit
         */
        // this.id = (milliSecond << (10 + 12)) + (shardId << 12) + sequence;
        // 时间戳左移22位 - timestamp+0{22} | 逻辑分片ID左移12位 - logicalShardId + 0{12} | 递增序列号
        this.id = this.milliSecond << TIMESTAMP_SHIFT | (shardId << LOGICAL_SHARD_ID_SHIFT) | sequence;
    }

    /**
     * 解析ID为原始数据，id --> [milliSecond,shareId,seq]
     * @param id ID值
     * @return IdObject [milliSecond,shareId,seq]
     */
    public static IdObject parse(long id) {
        return new IdObject(id);
    }

    /**
     * 构建ID，规则为：<br/>
     * <pre>
     *     1.毫秒数(miliSecond) = unix时间戳(second) * 1000 + 当前这一秒钟已经逝去的微秒数(microSecond)/1000
     *     2.得到的miliSecond数据[1439198068235]向左移22位,空出给shardId和seq值存放
     *     3.share限定10位宽度，可以有1024分区
     *     4.sequence设定宽度为12，可以有4096个选项
     * </pre>
     * @param second unix时间戳，单位为s
     * @param microSecond 当前这一秒钟已经逝去的微秒数
     * @param shardId 当前分区ID
     * @param sequence 序列数值
     * @return 理论上可以在当前这一毫秒内可以生产4096个ID
     */
    public static long buildId(long second, long microSecond, long shardId, long sequence) {
        return (second * ONE_MILLI_IN_MICRO_SECS + microSecond / ONE_MILLI_IN_MICRO_SECS) << TIMESTAMP_SHIFT | (shardId << LOGICAL_SHARD_ID_SHIFT) | sequence;
    }

    public long getId() {
        return id;
    }

    public long getMilliSecond() {
        return milliSecond;
    }

    public long getShardId() {
        return shardId;
    }

    public long getSequence() {
        return sequence;
    }

    @Override
    public String toString() {
        return "IdObject{" +
                "id=" + id +
                ", milliSecond=" + milliSecond +
                ", shardId=" + shardId +
                ", sequence=" + sequence +
                '}';
    }
}
